
#ifndef GLPP_STD140_H
#define GLPP_STD130_H

#include <tuple>
#include <glm\glm.hpp>

template <typename Ty>
struct layout_std140_type_traits
{
	constexpr static size_t base_alignment()
	{
		static_assert(false, "The type does not map to a GLSL type.");
	}

	constexpr static size_t size()
	{
		return base_alignment();
	}
};

template <>
struct layout_std140_type_traits<glm::i32>
{
	constexpr static size_t base_alignment()
	{
		return 4;
	}

	constexpr static size_t size()
	{
		return 4;
	}
};

template <>
struct layout_std140_type_traits<glm::f32>
{
	constexpr static size_t base_alignment()
	{
		return 4;
	}

	constexpr static size_t size()
	{
		return 4;
	}
};

template <>
struct layout_std140_type_traits<bool>
{
	constexpr static size_t base_alignment()
	{
		return 4;
	}

	constexpr static size_t size()
	{
		return 4;
	}
};

template <typename T, glm::precision P>
struct layout_std140_type_traits<glm::tvec2<T, P>>
{
	constexpr static size_t base_alignment()
	{
		return 4 * 2;
	}

	constexpr static size_t size()
	{
		return 4 * 2;
	}
};

template <typename T, glm::precision P>
struct layout_std140_type_traits<glm::tvec3<T, P>>
{
	constexpr static size_t base_alignment()
	{
		return 4 * 3;
	}

	constexpr static size_t size()
	{
		return 4 * 3;
	}
};

template <typename T, glm::precision P>
struct layout_std140_type_traits<glm::tvec4<T, P>>
{
	constexpr static size_t base_alignment()
	{
		return 4 * 4;
	}

	constexpr static size_t size()
	{
		return 4 * 4;
	}
};

template <typename T, glm::precision P>
struct layout_std140_type_traits<glm::tmat2x2<T, P>>
{
	constexpr static size_t base_alignment()
	{
		return 4 * 4;
	}

	constexpr static size_t size()
	{
		return 2 * 2 * layout_std140_type_traits<T>::size();
	}
};

template <typename T, glm::precision P>
struct layout_std140_type_traits<glm::tmat2x3<T, P>>
{
	constexpr static size_t base_alignment()
	{
		return 4 * 4;
	}

	constexpr static size_t size()
	{
		return 2 * 3 * layout_std140_type_traits<T>::size();
	}
};

template <typename T, glm::precision P>
struct layout_std140_type_traits<glm::tmat2x4<T, P>>
{
	constexpr static size_t base_alignment()
	{
		return 4 * 4;
	}

	constexpr static size_t size()
	{
		return 2 * 4 * layout_std140_type_traits<T>::size();
	}
};

template <typename T, glm::precision P>
struct layout_std140_type_traits<glm::tmat3x2<T, P>>
{
	constexpr static size_t base_alignment()
	{
		return 4 * 4;
	}

	constexpr static size_t size()
	{
		return 3 * 2 * layout_std140_type_traits<T>::size();
	}
};

template <typename T, glm::precision P>
struct layout_std140_type_traits<glm::tmat3x3<T, P>>
{
	constexpr static size_t base_alignment()
	{
		return 4 * 4;
	}

	constexpr static size_t size()
	{
		return 3 * 3 * layout_std140_type_traits<T>::size();
	}
};

template <typename T, glm::precision P>
struct layout_std140_type_traits<glm::tmat3x4<T, P>>
{
	constexpr static size_t base_alignment()
	{
		return 4 * 4;
	}

	constexpr static size_t size()
	{
		return 3 * 4 * layout_std140_type_traits<T>::size();
	}
};

template <typename T, glm::precision P>
struct layout_std140_type_traits<glm::tmat4x2<T, P>>
{
	constexpr static size_t base_alignment()
	{
		return 4 * 4;
	}

	constexpr static size_t size()
	{
		return 4 * 2 * layout_std140_type_traits<T>::size();
	}
};

template <typename T, glm::precision P>
struct layout_std140_type_traits<glm::tmat4x3<T, P>>
{
	constexpr static size_t base_alignment()
	{
		return 4 * 4;
	}

	constexpr static size_t size()
	{
		return 4 * 3 * layout_std140_type_traits<T>::size();
	}
};

template <typename T, glm::precision P>
struct layout_std140_type_traits<glm::tmat4x4<T, P>>
{
	constexpr static size_t base_alignment()
	{
		return 4 * 4;
	}

	constexpr static size_t size()
	{
		return 4 * 4 * layout_std140_type_traits<T>::size();
	}
};

template <typename Elem, size_t Size>
struct layout_std140_type_traits<std::array<Elem, Size>>
{
	constexpr static size_t base_alignment()
	{
		return layout_std140_type_traits<glm::vec4>::base_alignment();
	}

	constexpr static size_t size()
	{
		return base_alignment() * (Size - 1) + layout_std140_type_traits<glm::vec4>::size();
	}
};

/*
Calculate the offset(aligned offset) where a member of <std 140> layout struct
with base alignment [BaseAlignment] should be arranged,
the next arrangable offset is [BaseOffset].
*/
template <size_t BaseOffset, size_t BaseAlignment>
constexpr size_t calc_layout_std140_padding()
{
	return BaseOffset + (
		BaseAlignment - (BaseOffset % BaseAlignment == 0 ? BaseAlignment : BaseOffset % BaseAlignment));
}

/*
Calculate the offset(aligned offset) where a member of <std 140> layout struct
with base alignment [BaseAlignment] and size [Size] ends up,
the next arrangable offset is [BaseOffset].
*/
template <size_t BaseOffset, size_t BaseAlignment, size_t Size>
constexpr size_t calc_layout_std140_next_baseoffset()
{
	return calc_layout_std140_padding<BaseOffset, BaseAlignment>() + Size;
}

/*
Calculate the offset(aligned offset) where a member of <std 140> layout struct
with GLSL type [Ty] ends up,
the next arrangable offset is [BaseOffset].
*/
template <size_t BaseOffset, typename Ty>
constexpr size_t calc_layout_std140_next_baseoffset()
{
	return calc_layout_std140_next_baseoffset<
		BaseOffset,
		layout_std140_type_traits<Ty>::base_alignment(),
		layout_std140_type_traits<Ty>::size()
	>();
}

template <size_t BaseOffset, typename... Args>
struct layout_std140_impl
{
	constexpr static size_t size()
	{
		return BaseOffset;
	}

	template <size_t Index>
	constexpr static size_t offset_of()
	{
		static_assert(false, "Error [Index] when calling [offset_of].");
	}
};

template <size_t BaseOffset, typename Head, typename ...Remain>
struct layout_std140_impl<BaseOffset, Head, Remain...>
{
	constexpr static size_t size()
	{
		return layout_std140_impl<calc_layout_std140_next_baseoffset<BaseOffset, Head>(), Remain...>::size();
	}

	template <size_t Index>
	constexpr static size_t offset_of()
	{
		return layout_std140_impl<calc_layout_std140_next_baseoffset<BaseOffset, Head>(), Remain...>::offset_of<Index - 1>();
	}

	template <>
	constexpr static size_t offset_of<static_cast<size_t>(0)>()
	{
		return calc_layout_std140_padding<BaseOffset, layout_std140_type_traits<Head>::base_alignment()>();
	}
};

/* class layout_std140_struct
The [layout_std140_struct] class provide capabilies used for
calculating informations of memory buffer with layout <std140> that
GLSL accepts and can then be correctly map to corresponding GLSL structure.
The corresponding GLSL structure's member type, in this class, are specified by
template arguments [Args].
The mapping relation bettween GLSL type and type paramemeter accepted by this
class is as following:
------------------------------------------
GLSL Type         |         Type Parameter
------------------------------------------
int               |         glm::i32
float             |         glm::f32
vec3              |         glm::tvec3
vec4              |         glm::tvec4
mat4              |         glm::tmat4
<GLSL Array>      |         std::array
------------------------------------------
*/
template <typename... Args>
class layout_std140_struct
{
public:
	/* size()
	Return the size of the entire structure, in bytes.
	*/
	constexpr static size_t size()
	{
		return layout_std140_impl<static_cast<size_t>(0), Args...>::size();
	}

	/* offset_of()
	Return the offset(aligned offset) of [Index]th member of the structure, in bytes.
	The [Index] starts from 0.
	For example:
	Code fragment
	layout_std140_struct<float, int>::offset_of<1>()]
	returns the offset of second member(member_2) in GLSL structure
	struct InterfaceBlockName
	{
	float member_1;
	int member_2;
	};
	which, is 4.
	*/
	template <size_t Index>
	constexpr static size_t offset_of()
	{
		return layout_std140_impl<static_cast<size_t>(0), Args...>::offset_of<Index>();
	}

	/* member_type
	Return the type of [Index]th member of the structure.
	*/
	template <size_t Index>
	using member_type =
		typename std::tuple_element<Index, std::tuple<Args...>>::type;

	/* member_type_traits
	Equal to [layout_std140_type_traits<member_type<Index>>].
	*/
	template <size_t Index>
	using member_type_traits =
		layout_std140_type_traits<member_type<Index>>;

	/* buffer_type
	The char array type of which the variable can exactly hold
	entire data of structure, equal to [char buffer_type[size()]].
	*/
	typedef char buffer_type[size()];
};

template <typename... Args>
struct layout_std140_type_traits<layout_std140_struct<Args...>>
{
	constexpr static size_t base_alignment()
	{
		return layout_std140_type_traits<glm::vec4>::base_alignment();
	}

	constexpr static size_t size()
	{
		return layout_std140_struct<Args...>::size();
	}
};

#endif